﻿using System;
using Pfz.Extensions;

namespace Pfz.Serialization.BinaryBuiltIn
{
	/// <summary>
	/// Helper class to get a typed ArraySerializer.
	/// </summary>
	public static class ArraySerializer
	{
		/// <summary>
		/// Gets an instantiated ArraySerializer for the given element type.
		/// </summary>
		public static IItemSerializer GetForElementType(Type elementType)
		{
			var serializerType = typeof(ArraySerializer<>).MakeGenericType(elementType);
			var instanceField = serializerType.GetField("Instance");
			var untypedInstance = instanceField.GetValue(null);
			var serializer = (IItemSerializer)untypedInstance;
			return serializer;
		}
	}

	/// <summary>
	/// Serializes any array using the pattern: Size + serialization of each item.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public sealed class ArraySerializer<T>:
		ItemSerializer<T[]>
	{
		/// <summary>
		/// Gets the singleton instance.
		/// </summary>
		public static readonly ArraySerializer<T> Instance = new ArraySerializer<T>();

		private ArraySerializer()
		{
		}

		/// <summary>
		/// Serializes the array.
		/// </summary>
		public override void Serialize(ConfigurableSerializerBase serializer, T[] array)
		{
			var stream = serializer.Stream;
			stream.WriteCompressedInt32(array.Length);
			foreach(var arrayItem in array)
				serializer.InnerSerialize(arrayItem, typeof(T));
		}

		/// <summary>
		/// Deserializes the array.
		/// </summary>
		public override T[] Deserialize(ConfigurableSerializerBase deserializer)
		{
			var stream = deserializer.Stream;
			int count = stream.ReadCompressedInt32();
			T[] result = new T[count];
			for(int i=0; i<count; i++)
			{
				object deserialized = deserializer.InnerDeserialize(typeof(T));
				SerializationReference reference = deserialized as SerializationReference;
				if (reference == null)
					result[i] = (T)deserialized;
				else
				{
					var filler = new ReferenceFiller();
					filler.result = result;
					filler.resultIndex = i;
					deserializer.FillReference(reference.ReferenceId, filler.Fill);
				}
			}

			return result;
		}

		private sealed class ReferenceFiller
		{
			internal int resultIndex;
			internal T[] result;

			internal void Fill(object resultItem)
			{
				result[resultIndex] = (T)resultItem;
			}
		}
	}
}
